package cn.okcode.api.framework.dispatcher;

import cn.okcode.api.framework.ActionException;
import cn.okcode.api.framework.annotation.CPAutowired;
import cn.okcode.api.framework.annotation.CPController;
import cn.okcode.api.framework.annotation.CPService;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * IOC核心容器
 * <p>
 *
 * @author pyk13
 */
public class ApplicationContext {
    public static final Logger logger = LogManager.getLogger(ApplicationContext.class.getName());

    private final String[] scanPackages;
    protected final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    private static ApplicationContext current;
    private String jarPath;

    public static ApplicationContext getCurrent() {
        return ApplicationContext.current;
    }

    /**
     * 单例的IOC容器缓存
     */
    private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>();

    /**
     * 通用的IOC容器
     */
    private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>();

    public ApplicationContext(String[] scanPackages) {
        this.scanPackages = scanPackages;
        refresh();
        ApplicationContext.current = this;
    }

    public ApplicationContext(String jarPath, String[] scanPackages) {
        this.jarPath = jarPath;
        this.scanPackages = scanPackages;
        refresh();
        ApplicationContext.current = this;
    }

    public void refresh() {
        //1、定位，定位配置文件
        BeanDefinitionReader reader;
        if (logger.isDebugEnabled()) {
            logger.debug("扫描类");
        }
        if (StringUtils.isNotBlank(jarPath)) {
            reader = new BeanDefinitionReader(jarPath, this.scanPackages);
        } else {
            reader = new BeanDefinitionReader(this.scanPackages);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("加载类");
        }
        //2、加载配置文件，扫描相关的类，把它们封装成BeanDefinition
        List<BeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

        if (logger.isDebugEnabled()) {
            logger.debug("注册类");
        }
        //3、注册，把配置信息放到容器里面(伪IOC容器)
        doRegisterBeanDefinition(beanDefinitions);

        if (logger.isDebugEnabled()) {
            logger.debug("注入类");
        }
        //4、把不是延时加载的类，有提前初始化
        doAutowired();

        if (logger.isDebugEnabled()) {
            logger.debug("完成刷新");
        }
    }

    private void doAutowired() {
        //实例化对象
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            if (!beanDefinitionEntry.getValue().isLazyInit()) {
                instantiate(beanName);
            }
        }
        //注入
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            if (!beanDefinitionEntry.getValue().isLazyInit()) {
                BeanWrapper beanWrapper = factoryBeanInstanceCache.get(beanName);
                //3、注入
                populateBean(beanWrapper);
            }
        }
    }

    private void doRegisterBeanDefinition(List<BeanDefinition> beanDefinitions) {
        beanDefinitionMap.clear();
        for (BeanDefinition beanDefinition : beanDefinitions) {
            if (beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new ActionException("The “" + beanDefinition.getFactoryBeanName() + "” is exists!!");
            }
            beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }
    }

    public Object getBean(Class<?> beanClass) {
        return getBean(beanClass.getName());
    }

    public Object getBean(String beanName) {
        BeanWrapper beanWrapper = this.factoryBeanInstanceCache.get(beanName);
        if (beanWrapper == null) {
            return null;
        }
        return beanWrapper.getWrappedInstance();
    }

    public BeanWrapper instantiate(String beanName) {
        BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);

        if (beanDefinition == null) {
            return null;
        }

        Object instance = doInstantiateBean(beanDefinition);

        //3、把这个对象封装到BeanWrapper中
        BeanWrapper beanWrapper = new BeanWrapper(instance);

        //2、拿到BeanWrapper之后，把BeanWrapper保存到IOC容器中去
        this.factoryBeanInstanceCache.put(beanName, beanWrapper);

        return beanWrapper;
    }

    private void populateBean(BeanWrapper beanWrapper) {
        Object instance = beanWrapper.getWrappedInstance();

        Class<?> clazz = beanWrapper.getWrappedClass();
        //判断只有加了注解的类，才执行依赖注入
        if (!(clazz.isAnnotationPresent(CPController.class) || clazz.isAnnotationPresent(CPService.class))) {
            return;
        }

        //获得所有的fields
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(CPAutowired.class)) {
                continue;
            }

            CPAutowired autowired = field.getAnnotation(CPAutowired.class);

            String autowiredBeanName = autowired.value().trim();
            if ("".equals(autowiredBeanName)) {
                autowiredBeanName = field.getType().getName();
            }

            //强制访问
            field.setAccessible(true);

            try {
                BeanWrapper targetBean = this.factoryBeanInstanceCache.get(autowiredBeanName);
                if (targetBean == null) {
                    continue;
                }
                field.set(instance, targetBean.getWrappedInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                throw new ActionException("注入属性值出错");
            }
        }
    }

    private Object doInstantiateBean(BeanDefinition beanDefinition) {
        //1、拿到要实例化的对象的类名
        String className = beanDefinition.getBeanClassName();

        //2、反射实例化，得到一个对象
        Object instance = null;
        //假设默认就是单例
        if (this.factoryBeanObjectCache.containsKey(className)) {
            instance = this.factoryBeanObjectCache.get(className);
        } else {
            Class<?> clazz;
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                throw new ActionException("加载类" + className + "出错");
            }
            try {
                instance = clazz.getDeclaredConstructor().newInstance();
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                e.printStackTrace();
                throw new ActionException("实例化类" + className + "出错");
            }
            this.factoryBeanObjectCache.put(className, instance);
            this.factoryBeanObjectCache.put(beanDefinition.getFactoryBeanName(), instance);
        }

        return instance;
    }

    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[0]);
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }
}
